﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Clandestine;
using System.Drawing;
using System.Collections;
using System.Threading;

namespace Slick.MapEditor
{
    public class LayerTile
    {
        public int X;
        public int Y;
        public Texture Texture;
        public float Layer;
    }

    public class MassiveQuadSprite : Sprite
    {
        public bool Cursed = false; // if it's cursed, don't start any more curses. we wouldn't want to hurt mqs's feelings
        // (for more info about above see Layer.threadProcLateQuadRemove and the lines in the function preceding that one)

        public int TileCount = 0;

        public MassiveQuadSprite() : base() { }

        public MassiveQuadSprite(string filename) : base(filename) { }

        public MassiveQuadSprite(Texture texture) : base(texture) { }
    }

    public class Layer
    {
        private decimal depth;
        public decimal Depth
        {
            get { return depth; }
            set
            {
                depth = value;
                lock (Quads)
                {
                    foreach (Dictionary<int, MassiveQuadSprite> ds in Quads.Values)
                        foreach (Sprite sp in ds.Values)
                            sp.Layer = ((float)value);
                }
                lock (Scanlines)
                {
                    foreach (Dictionary<int, LayerTile> dilt in Scanlines.Values)
                        foreach (LayerTile lt in dilt.Values)
                            lt.Layer = ((float)value);
                }
            }
        }

        public int ID; // can have multiple layers at same depth, each need unique ID in editor atleast! (not saved/persistant between sessions)

        public Dictionary<int, Dictionary<int, LayerTile>> Scanlines = new Dictionary<int, Dictionary<int, LayerTile>>();

        public Dictionary<int, Dictionary<int, MassiveQuadSprite>> Quads = new Dictionary<int, Dictionary<int, MassiveQuadSprite>>();
        public const int QuadSize = 640; // i have reasons, ok?!  // TODO: adjust based on grid size!! i mean, FUCK!

        //private static Texture cuntTile = new Texture(

        public void SetTile(int tileX, int tileY, Texture newTile)
        {
            lock (Scanlines)
            {
                if (!Scanlines.ContainsKey(tileY))
                    Scanlines.Add(tileY, new Dictionary<int, LayerTile>());

                if (Scanlines[tileY].ContainsKey(tileX))
                {
                    Scanlines[tileY][tileX].Texture = newTile;
//                    Scanlines[tileY][tileX].Visible = true;
                }
                else
                {
                    Scanlines[tileY].Add(tileX, new LayerTile());
                    Scanlines[tileY][tileX].Texture = newTile;
                    Scanlines[tileY][tileX].X = tileX * Program.GridSize;
                    Scanlines[tileY][tileX].Y = tileY * Program.GridSize;
                    Scanlines[tileY][tileX].Layer = ((float)Depth);
                }

                // QUADS
                //Scanlines[tileY][tileX].Visible = false;
            }

            lock (Quads)
            {
                // JESUS FUCK
                int eggsx = tileX * Program.GridSize;
                int wryy = tileY * Program.GridSize;

                int quadx = eggsx / QuadSize;
                int quady = wryy / QuadSize;
                int quadoffx = eggsx % QuadSize;
                int quadoffy = wryy % QuadSize;

                if (!Quads.ContainsKey(quady))
                    Quads.Add(quady, new Dictionary<int, MassiveQuadSprite>());
                if (!Quads[quady].ContainsKey(quadx))
                {
                    MassiveQuadSprite sprite = new MassiveQuadSprite(new Texture(new Bitmap(QuadSize, QuadSize)));
                    sprite.X = quadx * QuadSize;
                    sprite.Y = quady * QuadSize;
                    sprite.Layer = (float)Depth;
                    sprite.Visible = true;
                    Quads[quady].Add(quadx, sprite);
                }

                // blit it!
                Quads[quady][quadx].GetTexture().MiniBlit(newTile.bitmap, quadoffx, quadoffy, false);
                Quads[quady][quadx].TileCount++;
            }
        }

        public void ClearAllTiles()
        {
            lock (Scanlines)
                lock (Quads)
                {
                    List<LayerTile> thingsToClear = new List<LayerTile>();

                    foreach (Dictionary<int, LayerTile> dilt in Scanlines.Values)
                        foreach (LayerTile lt in dilt.Values)
                            thingsToClear.Add(lt);

                    foreach (LayerTile t in thingsToClear)
                        ClearTile(t.X / Program.GridSize, t.Y / Program.GridSize);
                }
        }

        public void ClearTile(int tileX, int tileY)
        {
            lock (Scanlines)
            {
                if (!Scanlines.ContainsKey(tileY))
                    return;
                if (!Scanlines[tileY].ContainsKey(tileX))
                    return;

                Scanlines[tileY].Remove(tileX);
            }

            lock (Quads)
            {
                // JESUS FUCK
                int eggsx = tileX * Program.GridSize;
                int wryy = tileY * Program.GridSize;

                int quadx = eggsx / QuadSize;
                int quady = wryy / QuadSize;
                int quadoffx = eggsx % QuadSize;
                int quadoffy = wryy % QuadSize;

                if (!Quads.ContainsKey(quady) || !Quads[quady].ContainsKey(quadx))
                    return;
                
                // otherwise...
                Quads[quady][quadx].GetTexture().MiniBlit(new Bitmap(Program.GridSize, Program.GridSize), quadoffx, quadoffy, false);
                Quads[quady][quadx].TileCount--;

                // If the quad has no tiles, wait 1.5s and if it still has no tiles then remove it.
                // These quads _ARE_ relatively massive, and the user has an infinite amount of space in which to work (by moving the camera) --
                // ..so, if we end up creating quads for e.g. 8000x8000 pixels worth of screen space for e.g. the temp layer we are in DEEP SHIT.
                // However, we also wait 1.5s to ensure we're not too eager.  We don't sample TileCount more than once over the 1.5s period b/c
                //  if we remove one quad once ever too enthusiastically i'm not going to lose any sleep over this.
                if (Quads[quady][quadx].TileCount == 0 && !Quads[quady][quadx].Cursed)
                {
                    Quads[quady][quadx].Cursed = true;
                    ThreadPool.QueueUserWorkItem(new WaitCallback(threadProcLateQuadRemove), new Point(quadx, quady));
                }
            }
        }

        private void threadProcLateQuadRemove(object quado)
        {
            Point quadd = (Point)quado;
            int quadx = quadd.X;
            int quady = quadd.Y;
            
            Thread.Sleep(1500);

            lock (Quads)
            {
                if (Quads[quady][quadx].TileCount == 0)
                {
                    Quads[quady][quadx].Dispose();
                    Quads[quady].Remove(quadx);

                    if (Quads[quady].Count == 0)
                        Quads.Remove(quady);
                }
                else
                    Quads[quady][quadx].Cursed = false; // ^^
            }
        }

        public Layer Clone(decimal newDepth)
        {
            Layer newLayer = new Layer();
            newLayer.Depth = newDepth;
            newLayer.Scanlines = new Dictionary<int, Dictionary<int, LayerTile>>();

            // Copy da tiles
            foreach (int disi in this.Scanlines.Keys)
            {
                newLayer.Scanlines.Add(disi, new Dictionary<int, LayerTile>());
                Dictionary<int, LayerTile> dis = newLayer.Scanlines[disi];

                foreach (int disii in this.Scanlines[disi].Keys)
                {
                    LayerTile spSrc =  this.Scanlines[disi][disii];
                    LayerTile spDst = new LayerTile();
                    spDst.Texture = spSrc.Texture;
                    spDst.X = spSrc.X;
                    spDst.Y = spSrc.Y;
                    spDst.Layer = (float)newDepth;
                    dis.Add(disii, spDst);
                }
            }

            throw new NotImplementedException("can't clone quads yet");

            return newLayer;
        }

        public override string ToString()
        {
            return Depth.ToString();
        }

        public void Dispose()
        {
            lock (Scanlines)
                lock (Quads)
            {
                foreach (Dictionary<int, MassiveQuadSprite> dis in Quads.Values)
                    foreach (Sprite s in dis.Values)
                        s.Dispose();
                Scanlines = new Dictionary<int, Dictionary<int, LayerTile>>();
                Quads = new Dictionary<int, Dictionary<int, MassiveQuadSprite>>();
            }
        }

        public void HideEverything()
        {
            lock (Quads)
            {
                foreach (Dictionary<int, MassiveQuadSprite> dis in Quads.Values)
                    foreach (Sprite s in dis.Values)
                        s.Visible = false;
                //Scanlines = new Dictionary<int, Dictionary<int, Sprite>>();
            }
        }

        public void ShowEverything()
        {
            lock (Quads)
            {
                foreach (Dictionary<int, MassiveQuadSprite> dis in Quads.Values)
                    foreach (MassiveQuadSprite s in dis.Values)
                        s.Visible = true;
            }
        }

        public int GetMinLine()
        {
            lock (Scanlines)
                if (Scanlines.Keys.Count != 0)
                    return (Scanlines.Keys.Min() < 0 ? Scanlines.Keys.Min() : 0);
            return 0;
        }

        public int GetMaxLine()
        {
            lock (Scanlines)
                if (Scanlines.Keys.Count != 0)
                    return Scanlines.Keys.Max();
            return 0;
        }

        public int GetMinX()
        {
            lock (Scanlines)
            {
                int minx = 0;

                foreach (Dictionary<int, LayerTile> dis in Scanlines.Values)
                {
                    if (dis.Count == 0)
                        continue;

                    int a = dis.Keys.Min();
                    if (a < minx)
                        minx = a;
                }

                return minx;
            }
        }

        public int GetMaxX()
        {
            lock (Scanlines)
            {
                int maxx = 0;

                foreach (Dictionary<int, LayerTile> dis in Scanlines.Values)
                {
                    if (dis.Count == 0)
                        continue;

                    int a = dis.Keys.Max();
                    if (a > maxx)
                        maxx = a;
                }

                return maxx;
            }
        }

        public void Open(string layerData)
        {
            // Split into lines
            string[] lines = layerData.Trim().Split(new string[] { "\r\n" }, StringSplitOptions.None);

            for (int y = 0; y < lines.Length; y++)
            {
                // Only do something if it's not a blank line
                if ((lines[y] != ". . . ") && (lines[y].Trim() != string.Empty)) // where the string.Empty every alternate line comes in, I don't know. .Split()?
                {
                    string[] splitLine = lines[y].Trim().Split(" ".ToCharArray());

                    // Set each tile
                    for (int x = 0; x < splitLine.Length; x++)
                    {
                        if (splitLine[x] != ".")
                        {
                            string[] splitTile = splitLine[x].Split(".".ToCharArray());
                            if (splitTile.Length > 2)
                                throw new Exception("wtf mate. ^.^");

                            // Get tileset name..
                            splitTile[0] = Tilesets.LoadedTilesets[int.Parse(splitTile[0])];

                            // Set the tile, and we're done!
                            if (!Texture.TextureExists(splitTile[0] + "!" + splitTile[1]))
                                Tilesets.CutSingleTile(splitTile[0], int.Parse(splitTile[1]));
                            this.SetTile(x, y, Texture.GetTexture(splitTile[0] + "!" + splitTile[1]));
                        }
                    }
                }
            }
        }

        public void Save(System.Xml.XmlTextWriter xml, int minline, int maxline, int minx, Dictionary<string, int> tilesetToIndexMapping)
        {
            // Start the layer tag and shizz.
            xml.WriteStartElement("layer");
            xml.WriteAttributeString("depth", Depth.ToString());
            StringBuilder sb = new StringBuilder();

            // Convert the layer to text!
            lock (Scanlines)
            {
                for (int y = minline; y <= maxline; y++)
                {
                    if (!Scanlines.Keys.Contains(y) || Scanlines[y].Keys.Count == 0)
                        sb.AppendLine(". . . ");
                    else
                    {
                        string line = "";

                        for (int x = minx; x <= Scanlines[y].Keys.Max(); x++)
                        {
                            if (!Scanlines[y].Keys.Contains(x))
                                line += ". ";
                            else
                            {
                                string tilename = Scanlines[y][x].Texture.Filename;

                                int li = tilename.LastIndexOf('!');
                                string tilesheetName = tilename.Substring(0, li);
                                string tileNumber = tilename.Substring(li + 1);

                                line += tilesetToIndexMapping[tilesheetName].ToString()
                                    + "." + tileNumber + " ";
                            }
                        }

                        sb.AppendLine(line);
                    }
                }

            }

            // Tidy up
            xml.WriteCData(sb.ToString());
            xml.WriteEndElement(); // </layer>
        }
    }
}
